package au.edu.adelaide.mips_soft_core.board_interface_pc.core;

import java.util.ArrayList;

public class Processor {
	
	static final byte DEBUG_OP_HALF_STEP = 1;
	static final byte GET_PC = 2;
	static final byte GET_REGISTER_AND_PC = 3;
	
	public int pc;
	public int[] registers = new int[32];
	private int[] temp = new int[32];
	public boolean[] registersChanged = new boolean[32];
	
	public int[] coProcessor0Registers = new int[18];
	
	boolean upToDate = false;
	
	ArrayList<ProcessorListener> listeners = new ArrayList<ProcessorListener>();
	
	public Device device; 
	public final byte processorNumber;
	
	public Processor(Device device, byte processorNumber){
		this.device = device;
		this.pc = 0x00000000;
		this.processorNumber = processorNumber;
	}
	
	public void addProcessorListener(ProcessorListener listener){
		this.listeners.add(listener);
	}
	
	public void removeProcessorListener(ProcessorListener listener){
		this.listeners.remove(listener);
	}
	
	/**
	 * call when entrying debug mode
	 */
	public void initalise(){
		getPCAndRegisters();
	}
	
	public void getPCAndRegisters(){
		new Thread(){
			public void run(){
				try{
					getPCAndRegistersBlocking();
					for(int i = 0; i < listeners.size(); i++){
						listeners.get(i).PCChanged();
						listeners.get(i).registerChanged();
					}
				}catch(Exception ex){
					ex.printStackTrace();
				}
			}
			
		}.start();
	}
	
	public void getPCAndRegistersBlocking() throws Exception{
		device.lock.acquire();
		if(!this.device.doOperation(Device.OPERATION_DEBUG1, "DEBUG1")){
			device.lock.release();
			return;
		}
		if(!this.device.doOperation(GET_REGISTER_AND_PC, "GETTING PC")){
			device.lock.release();
			return;
		}
		if(!this.device.doOperation(this.processorNumber, "Couldn't select processor")){
			device.lock.release();
			return;
		}
		if(!this.device.getIntS(this.temp)){
			System.out.println("Couldn't get registers");
			device.lock.release();
			return;
		}
		
		
		int[] results = new int[1];
		if(!this.device.getIntS(results)){
			System.out.println("couldn't get PC of processor");
			device.lock.release();
			return;
		}
		compare(this.temp, this.registers, this.registersChanged);
		int[] old = this.registers;
		this.registers = temp;
		this.temp = old;
		this.pc = results[0];
		byte[] finishByte = new byte[1];
		if(!this.device.getByte(finishByte)){
			System.out.println("couldn't get finish byte");
			device.lock.release();
			return;
		}
		byte acceptanceByte = (byte)0xAF;
		if(finishByte[0] != acceptanceByte){
			System.out.println("failed to get correct finish byte");
		}
		device.lock.release();
	}
	
	private void compare(int[] a1, int[] a2, boolean[] comp){
		for(int i = 0; i < a1.length; i++){
			comp[i] = a1[i] == a2[i];
		}
	}
	
	
	
	public void step(){
		new Thread(){
			public void run(){
				try{
					stepBlocking();
					for(int i = 0; i < listeners.size(); i++){
						listeners.get(i).outDated();
					}
				}catch(Exception ex){
					ex.printStackTrace();
				}
			}
			
		}.start();
	}
	
	public void stepBlocking() throws Exception{
		halfStepBlocking();
		halfStepBlocking();
	}
	

	
	public void halfStepBlocking() throws Exception{
		device.lock.acquire();
		if(!device.doOperation(Device.OPERATION_DEBUG1, "Debug1")){
			device.lock.release();
			return;
		}
		if(!device.doOperation(DEBUG_OP_HALF_STEP, "Debug1:HalfStep")){
			device.lock.release();
			return;
		}
		if(!device.doOperation(processorNumber, "Debug1:HalfStep:PerformHalfStep")){
			device.lock.release();
			return;
		}
		device.lock.release();

	}
	
	public int getRegisterValue(int regNumber){
		return this.registers[regNumber];
	}
	
	public int getPC(){
		return this.pc;
	}

}
